/*
* JS Application
*
* @package Design by Contract extension
* @category JSA
* @author $Author: sheiko $
* @version $Id: jquery.jsa-dbc.js, v 1.0 $
* @license GNU
* @copyright (c) Dmitry Sheiko http://www.dsheiko.com
*/
(function( $ ) {
    /**
     * Extends BaseAbstract with "contracts" property processor
     *
     * Contract syntax
     * var ConcreteContract = {
     *    methodA : ["string", MyObject],
     *    methodB : {
     *         onEntry:  ["string", MyObject],
     *         validators: [function(arg){}],
     *         onExit: "string"
     *     }
     * }
     * Allowed types:
     * boolean, number, string, array, object, function
     */
    $.jsa.BasePropertyProcessor.processContractsProperty = function(scope) {
        var _self = $.jsa.BasePropertyProcessor.processContractsProperty,
            _overrideMethod = function(method, contract, scope) {
                var orig = scope[method],
                sourceName = scope.name + "." + method;
                // If the contract in the form of interface
                // "methodA" : ["string", MyObject]
                if ($.isArray(contract)) {
                    var contract = {
                        onEntry: contract,
                        validators: [],
                        onExit: null
                    };
                }
                contract.validators = contract.validators || [];
                if (!$.isArray(contract.onEntry) || !$.isArray(contract.validators)) {
                    throw new TypeError(sourceName
                        + ": supplied contract property is of invalid type");
                }
                scope[method] = function() {
                    if (contract.onEntry.length !== arguments.length) {
                        throw new TypeError(sourceName
                            + ": argument number does not match the contract");
                    }
                    for (var i in arguments) {
                        // OnEntry
                        if (!_self.matchArgTypeHint(arguments[i], contract.onEntry[i])) {
                            throw new TypeError(sourceName + ": argument #" + (i * 1 + 1)
                                + " is required to be a "
                                + (typeof(contract.onEntry[i]['instanceOf']) !== 'undefined' ?
                                contract.onEntry[i].name : contract.onEntry[i]));
                        }
                        // Validators
                        if (typeof(contract.validators[i]) === "function"
                             && !contract.validators[i](arguments[i])) {
                            throw new RangeError(sourceName + ": argument #" + (i * 1 + 1)
                                + " is outside of its valid range");
                        }
                    }
                    var out = orig.apply(scope, arguments);
                    // OnExit
                    if (contract.onExit !== null && !_self.matchArgTypeHint(out, contract.onExit)) {
                        throw new TypeError(sourceName + ": return value is required to be a "
                            + contract.onExit);
                    }
                    return out;
                }
            };
        if (scope.contracts) {
            $.each(scope.contracts, function(method, contract){
                if ((typeof scope[method] === 'undefined')) {
                    throw new SyntaxError("One of the contracts, the object agreed on, "
                        + "contains abstract method '"
                        + method + "' and it must be implemented by the object");
                }
                _overrideMethod(method, contract, scope);
            });
        }
    };
    /**
     * processContractsProperty helper
     * @param mixed arg
     * @param mixed typeHint
     * @throws Exception
     */
    $.jsa.BasePropertyProcessor.processContractsProperty.matchArgTypeHint = function(arg, typeHint) {
        if (typeof(typeHint['instanceOf']) !== "undefined") {
            if (typeof(arg['instanceOf']) === "undefined" || !arg.instanceOf(typeHint.name)) {
                return false;
            }
        } else if (typeof(typeHint) === "string") {
            if (typeHint === "array" && !$.isArray(arg)) {
                return false;
            } else if (typeof arg !== typeHint) {
                return false;
            }
        }
        return true;
    };

})( jQuery );
